﻿/*===========================================================================================
 * 
 *  Author          : Arturo Salvamante
 * 
 *  File            : ACR1222U.cs
 * 
 *  Copyright (C)   : Advanced Card System Ltd
 * 
 *  Description     : Contains Methods and Properties for ACR1222U SmartCard Reader
 * 
 *  Date            : June 03, 2011
 * 
 *  Revision Traile : [Author] / [Date if modification] / [Details of Modifications done]
 * 
 * 
 * =========================================================================================*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ACS
{
    public struct LedBuzzerBehavior
    {
        public bool EnableICCActivationStatusLed;
        public bool EnablePICCPollingStatusLED;
        public bool EnablePICCActivationStatusLed;
        public bool EnableCardInsRemEventsBuzzer;
        public bool EnableCardOperatingBlinkingLed;

        public LedBuzzerBehavior(bool defVal)
        {
            this.EnableICCActivationStatusLed = defVal;
            this.EnablePICCPollingStatusLED = defVal;
            this.EnablePICCActivationStatusLed = defVal;
            this.EnableCardInsRemEventsBuzzer = defVal;
            this.EnableCardOperatingBlinkingLed = defVal;
        }
    }

    public enum KEYTYPES
    {
        ACR122_KEYTYPE_A = 96,
        ACR122_KEYTYPE_B = 97,
    }

    public enum CHIP_TYPE
    {
        UNKNOWN = 0,
        MIFARE_1K = 1,
        MIFARE_4K = 2,
        MIFARE_ULTRALIGHT = 3,
    }

    public class LedState
    {
        public LedState(bool red, bool green)
        {
            _red = red;
            _green = green;
        }

        public LedState(byte returnFromCard)
        {
            if ((returnFromCard & 0x01) == 0x01)
                _red = true;

            if ((returnFromCard & 0x02) == 0x02)
                _green = true;
        }

        private bool _red = false;
        public bool red
        {
            get { return _red; }
            set { _red = value; }
        }

        private bool _green = false;
        public bool green
        {
            get { return _green; }
            set { _green = value; }
        }
    }

    public class ReaderInterfaceStatus
    {
        public ReaderInterfaceStatus(bool defaultValue)
        {
            refreshIcc = defaultValue;
            refreshPicc = defaultValue;
            refreshSam = defaultValue;
        }

        public ReaderInterfaceStatus(byte returnFromcard)
        {
            if ((returnFromcard & 0x01) == 0x01)
                refreshIcc = true;

            if ((returnFromcard & 0x02) == 0x02)
                refreshPicc = true;

            if ((returnFromcard & 0x04) == 0x04)
                refreshSam = true;
        }

        public bool refreshIcc = false;
        public bool refreshPicc = false;
        public bool refreshSam = false;
    }

    public class CardInsertinCounter
    {
        public CardInsertinCounter()
        {

        }

        public CardInsertinCounter(byte[] returnFromcard)
        {
            if (returnFromcard.Length < 4)
                throw new Exception("Invalid data from card");

            iccInsertionCounterArray = returnFromcard.Take(2).ToArray();
            pIccInsertionCounterArray = returnFromcard.Skip(2).Take(2).ToArray();
        }

        private UInt16 _iccInsertionCounter = 0;
        public UInt16 iccInsertionCounter
        {
            get { return _iccInsertionCounter; }
            set { _iccInsertionCounter = value; }
        }
        public byte[] iccInsertionCounterArray
        {
            get 
            {
                byte[] buffer = Helper.intToByte(_iccInsertionCounter);
                Array.Reverse(buffer);
                return buffer.Take(2).ToArray();
            }
            set 
            {
                _iccInsertionCounter = (UInt16)Helper.byteToInt(value, true);
            }
        }

        private UInt16 _pIccInsertionCounter = 0;
        public UInt16 pIccInsertionCounter
        {
            get { return _pIccInsertionCounter; }
            set { _pIccInsertionCounter = value; }
        }
        public byte[] pIccInsertionCounterArray
        {
            get
            {
                byte[] buffer = Helper.intToByte(_iccInsertionCounter);
                Array.Reverse(buffer);
                return buffer.Take(2).ToArray();
            }
            set
            {
                _pIccInsertionCounter = (UInt16)Helper.byteToInt(value, true);
            }
        }

    }

    public class Acr1222U
    {
        private PcscItem _pcscConnection;
        public PcscItem pcscConnection
        {
            get { return _pcscConnection; }
            set { _pcscConnection = value; }
        }

        const uint IOCTL_SMARTCARD_ACR128_ESCAPE_COMMAND = (uint)PCSCProvider.FILE_DEVICE_SMARTCARD + 2079 * 4;
        const uint IOCTL_CCID_ESCAPE = (uint)PCSCProvider.FILE_DEVICE_SMARTCARD + 3500 * 4;

        public Acr1222U(PcscItem pcsc)
        {
            _pcscConnection = pcsc;
        }

        private uint _escapeCommandCode = IOCTL_SMARTCARD_ACR128_ESCAPE_COMMAND;
        public uint escapeCommandCode
        {
            get { return _escapeCommandCode; }
            set { _escapeCommandCode = value; }
        }

        public byte[] getCardSerialNumber()
        {
            Apdu apdu;
            byte[] CardSerial;
            int retCode;

            apdu = new Apdu();
            apdu.setCommand(new byte[] { 0xFF, 0xCA, 0x00, 0x00, 0x00 });
            apdu.lengthExpected = 20;

            retCode = pcscConnection.sendApdu(ref apdu);
            if (retCode != PCSCProvider.SCARD_S_SUCCESS)
                return null;

            if (apdu.sw[0] != 0x90)
                return null;

            CardSerial = new byte[apdu.receiveData.Length];
            Array.Copy(apdu.receiveData, CardSerial, CardSerial.Length);

            return CardSerial;

        }

        public bool loadAuthKey(byte[] key, byte keyNumber)
        {
            Apdu apdu;
            int retCode;

            if (keyNumber > 0x01)
                throw new Exception("Key number is invalid");

            if (key.Length != 6)
                throw new Exception("Invalid key length");


            apdu = new Apdu();
            apdu.setCommand(new byte[] { 0xFF, 0x82, 0x00, keyNumber, 0x06 });
            apdu.sendData = key;

            retCode = pcscConnection.sendApdu(ref apdu);
            if (retCode != PCSCProvider.SCARD_S_SUCCESS)
                throw new PcscException(retCode);

            if (apdu.sw[0] != 0x90)
                return false;

            return true;
        }

        public bool authenticate(byte blockNum, KEYTYPES keyType, byte KeyNum)
        {
            Apdu apdu;
            int retCode;

            if (KeyNum < 0x00 && KeyNum > 0x20)
                throw new Exception("Key number is invalid");

            apdu = new Apdu();
            apdu.setCommand(new byte[] { 0xFF, 0x86, 0x00, 0x00, 0x05 });
            apdu.sendData = new byte[] { 0x01, 0x00, (byte)blockNum, (byte)keyType, KeyNum };

            retCode = pcscConnection.sendApdu(ref apdu);

            if (retCode != PCSCProvider.SCARD_S_SUCCESS)
                throw new PcscException(retCode);

            if (apdu.sw[0] != 0x90)
                return false;

            return true;
        }

        public void setLedState(LedState ledState)
        {
            Apdu apdu = new Apdu();
            int returnCode;

            apdu.lengthExpected = 100;
            apdu.sendData = new byte[6];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x29;
            apdu.sendData[4] = 0x01;

            if (ledState.red)
                apdu.sendData[5] = 0x01;

            if (ledState.green)
                apdu.sendData[5] |= 0x02;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);
        }

        public LedState getLedState()
        {
            Apdu apdu = new Apdu();
            int returnCode;

            apdu.lengthExpected = 100;
            apdu.sendData = new byte[5];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x29;
            apdu.sendData[4] = 0x00;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);


            return new LedState(apdu.receiveData[5]);

        }
        
        public void setBuzzer(byte duration)
        {
            Apdu apdu = new Apdu();
            int returnCode;

            apdu.lengthExpected = 10;

            apdu.sendData = new byte[6];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x28;
            apdu.sendData[4] = 0x01;
            apdu.sendData[5] = duration;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);

            if (apdu.receiveData == null || apdu.receiveData.Length < 6)
                throw new Exception("Unable to set led");
        }

        public byte readBuzzerState()
        {
            int returnCode;

            Apdu apdu = new Apdu();
            apdu.lengthExpected = 6;

            apdu.sendData = new byte[5];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x28;
            apdu.sendData[4] = 0x00;
            
            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);


            if (apdu.receiveData == null || apdu.receiveData.Length < 6)
                throw new Exception("Unable to set buzzer");

            return apdu.receiveData[5];
        }

        public int setExclusiveMode(bool useBoth)
        {
            Apdu apdu = new Apdu();
            apdu.lengthExpected = 20;
            apdu.sendData = new byte[3];
            apdu.sendData[0] = 0x2B;
            apdu.sendData[1] = 0x01;
            apdu.sendData[2] = (byte)((useBoth) ? 0x00 : 0x01);

            return pcscConnection.sendCardControl(ref apdu, IOCTL_SMARTCARD_ACR128_ESCAPE_COMMAND);
        }

        public void setLedBuzzerBehavior(LedBuzzerBehavior acr128Settings)
        {
            int returnCode;
            Apdu apdu = new Apdu();

            apdu.lengthExpected = 10;
            apdu.sendData = new byte[6];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x21;
            apdu.sendData[4] = 0x01;

            if (acr128Settings.EnableICCActivationStatusLed)
                apdu.sendData[5] |= 0x01;

            if (acr128Settings.EnablePICCPollingStatusLED)
                apdu.sendData[5] |= 0x02;

            if (acr128Settings.EnablePICCActivationStatusLed)
                apdu.sendData[5] |= 0x04;

            if (acr128Settings.EnableCardInsRemEventsBuzzer)
                apdu.sendData[5] |= 0x08;

            if (acr128Settings.EnableCardOperatingBlinkingLed)
                apdu.sendData[5] |= 0x80;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);

            if (apdu.receiveData == null || apdu.receiveData.Length < 6)
                throw new Exception("Unable to set led and buzzer");
        }

        public LedBuzzerBehavior getLedBuzzerBehavior()
        {
            int returnCode;
            byte defaultBehavior = 0;
            LedBuzzerBehavior returnValue;

            Apdu apdu = new Apdu();
            apdu.lengthExpected = 10;
            apdu.sendData = new byte[5];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x21;
            apdu.sendData[4] = 0x00;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);

            if (apdu.receiveData == null || apdu.receiveData.Length < 6)
                throw new Exception("Unable to set led and buzzer");
            
            defaultBehavior = apdu.receiveData[5];

            returnValue = new LedBuzzerBehavior(false);

            if ((defaultBehavior & 0x01) == 0x01)
                returnValue.EnableICCActivationStatusLed = true;

            if ((defaultBehavior & 0x02) == 0x02)
                returnValue.EnablePICCPollingStatusLED = true;

            if ((defaultBehavior & 0x04) == 0x04)
                returnValue.EnablePICCActivationStatusLed = true;

            if ((defaultBehavior & 0x08) == 0x08)
                returnValue.EnableCardInsRemEventsBuzzer = true;

            if ((defaultBehavior & 0x80) == 0x80)
                returnValue.EnableCardOperatingBlinkingLed = true;

            return returnValue;
        }

        public void disableAutoPolling()
        {
            Apdu apdu = new Apdu();
            apdu.lengthExpected = 6;
            apdu.sendData = new byte[3];
            apdu.sendData[0] = 0x23;
            apdu.sendData[1] = 0x01;
            apdu.sendData[2] = 0x9E;
            pcscConnection.sendCardControl(ref apdu, IOCTL_SMARTCARD_ACR128_ESCAPE_COMMAND);
        }

        public void setSAMStartupFiDi(byte fiDi)
        {
            int returnCode;

            Apdu apdu = new Apdu();
            apdu.lengthExpected = 6;

            apdu.sendData = new byte[6];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x08;
            apdu.sendData[4] = 0x01;
            apdu.sendData[5] = fiDi;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);


            if (apdu.receiveData == null || apdu.receiveData.Length < 6)
                throw new Exception("Unable to set sam startup FiDi");
        }

        public void setPiccTimeout(byte timeout)
        {
            int returnCode;

            Apdu apdu = new Apdu();
            apdu.lengthExpected = 10;

            apdu.sendData = new byte[5];
            apdu.sendData[0] = 0xFF;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x41;
            apdu.sendData[3] = timeout;
            apdu.sendData[4] = 0x00;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);

            if (apdu.receiveData.Length >= 7)
            {
                if (apdu.receiveData[5] != 0x90)
                    throw new Exception("Set PICC Timeout failed");
            }
            else
            {
                if (apdu.receiveData[0] != 0x90)
                    throw new Exception("Set PICC Timeout failed");
            }
        }


        public byte getSAMStartupFiDi()
        {
            int returnCode;

            Apdu apdu = new Apdu();
            apdu.lengthExpected = 6;

            apdu.sendData = new byte[5];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x08;
            apdu.sendData[4] = 0x00;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);


            if (apdu.receiveData == null || apdu.receiveData.Length < 6)
                throw new Exception("Unable to set sam startup FiDi");

            return apdu.receiveData[5];
        }

        public void refreshInterfaceStatus(ReaderInterfaceStatus interfaceStatus)
        {
            int returnCode;
            byte status = 0;

            if (interfaceStatus.refreshIcc)
                status |= 0x01;

            if (interfaceStatus.refreshPicc)
                status |= 0x02;

            if (interfaceStatus.refreshSam)
                status |= 0x04;


            Apdu apdu = new Apdu();
            apdu.lengthExpected = 6;

            apdu.sendData = new byte[6];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x2D;
            apdu.sendData[4] = 0x01;
            apdu.sendData[5] = status;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);
            
            if (apdu.receiveData == null || apdu.receiveData.Length < 6)
                throw new Exception("Unable to set sam startup FiDi");
        }

        public void setDeviceSerialNumber(byte[] deviceSerialNumber)
        {
            int returnCode;

            if (deviceSerialNumber.Length != 20)
                Array.Resize(ref deviceSerialNumber, 20);

            Apdu apdu = new Apdu();
            apdu.lengthExpected = 7;

            apdu.sendData = new byte[deviceSerialNumber.Length + 5];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x26;
            apdu.sendData[4] = (byte)deviceSerialNumber.Length;

            Array.Copy(deviceSerialNumber, 0, apdu.sendData, 5, deviceSerialNumber.Length);

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);
            
            if (apdu.receiveData == null || apdu.receiveData.Length < 7 || apdu.receiveData[5] != 0x90)
                throw new Exception("Unable to set sam startup FiDi");
        }

        public byte[] getDeviceSerialNumber()
        {
            int returnCode;

            
            Apdu apdu = new Apdu();
            apdu.lengthExpected = 30;

            apdu.sendData = new byte[5];
            apdu.sendData[0] = 0xFF;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x49;
            apdu.sendData[3] = 0x00;
            apdu.sendData[4] = 0x00;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);

            if (apdu.receiveData == null || apdu.receiveData.Length < 25)
                throw new Exception("Unable to set sam startup FiDi");

            return apdu.receiveData.Skip(5).Take(apdu.receiveData.Length - 5).ToArray();
        }

        public void initializeCardInsertionCounter(CardInsertinCounter cardInsertionvalue)
        {
            int returnCode;


            Apdu apdu = new Apdu();
            apdu.lengthExpected = 5;

            apdu.sendData = new byte[9];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x09;
            apdu.sendData[4] = 0x04;

            Array.Copy(cardInsertionvalue.iccInsertionCounterArray, 0, apdu.sendData, 5, 2);

            Array.Copy(cardInsertionvalue.pIccInsertionCounterArray, 0, apdu.sendData, 7, 2);
            
            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);

            if (apdu.receiveData == null || apdu.receiveData.Length < 5)
                throw new Exception("Unable to initialize card insertion counter");
        }

        public CardInsertinCounter updateCardInsertionCounter()
        {
            int returnCode;


            Apdu apdu = new Apdu();
            apdu.lengthExpected = 10;

            apdu.sendData = new byte[5];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x0A;
            apdu.sendData[4] = 0x00;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);

            if (apdu.receiveData == null || apdu.receiveData.Length < 9)
                throw new Exception("Unable to update card insertion counter");

            return new CardInsertinCounter(apdu.receiveData.Skip(5).Take(4).ToArray());
        }

        public CardInsertinCounter readCardInsertionCounter()
        {
            int returnCode;
            
            Apdu apdu = new Apdu();
            apdu.lengthExpected = 10;

            apdu.sendData = new byte[5];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x09;
            apdu.sendData[4] = 0x00;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);

            if (apdu.receiveData == null || apdu.receiveData.Length < 9)
                throw new Exception("Unable to read card insertion counter");

            return new CardInsertinCounter(apdu.receiveData.Skip(5).Take(4).ToArray());

        }

        public ReaderInterfaceStatus getRefreshInterfaceStatus()
        {
            int returnCode;

            Apdu apdu = new Apdu();
            apdu.lengthExpected = 6;

            apdu.sendData = new byte[5];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x2D;
            apdu.sendData[4] = 0x00;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);


            if (apdu.receiveData == null || apdu.receiveData.Length < 6)
                throw new Exception("Unable to set sam startup FiDi");

            return new ReaderInterfaceStatus(apdu.receiveData[5]);
        }

        
        public CHIP_TYPE getChipType()
        {
            int rdrLen = 0, retCode, protocol = pcscConnection.activeProtocol;
            int pdwSate = 0, ATRLen = 33;
            byte[] ATR = new byte[100];
            CHIP_TYPE cardType = CHIP_TYPE.UNKNOWN;


            retCode = PCSCProvider.SCardStatus(pcscConnection.cardHandle, pcscConnection.readerName, ref rdrLen, ref pdwSate,
                                                ref protocol, ATR, ref ATRLen);

            if (retCode != PCSCProvider.SCARD_S_SUCCESS)
                throw new PcscException(retCode);

            pcscConnection.activeProtocol = protocol;

            if (ATR.Length < 33)
                return CHIP_TYPE.UNKNOWN;

            Array.Resize(ref ATR, ATRLen);

            if (ATR[13] == 0x00 && ATR[14] == 0x01)
                cardType = CHIP_TYPE.MIFARE_1K;
            else if (ATR[13] == 0x00 && ATR[14] == 0x02)
                cardType = CHIP_TYPE.MIFARE_4K;
            else if (ATR[13] == 0x00 && ATR[14] == 0x03)
                cardType = CHIP_TYPE.MIFARE_ULTRALIGHT;
            else
                cardType = CHIP_TYPE.UNKNOWN;

            return cardType;
        }

        public string getFirmwareVersion()
        {
            Apdu apdu = new Apdu();
            int returnCode = 0;
            
            apdu.lengthExpected = 255;
            apdu.sendData = new byte[5];
            apdu.sendData[0] = 0xE0;
            apdu.sendData[1] = 0x00;
            apdu.sendData[2] = 0x00;
            apdu.sendData[3] = 0x18;
            apdu.sendData[4] = 0x00;

            returnCode = pcscConnection.sendCardControl(ref apdu, escapeCommandCode);
            if (returnCode != 0)
                throw new PcscException(returnCode);


            return ASCIIEncoding.ASCII.GetString(apdu.receiveData.Skip(5).Take(apdu.receiveData[4]).ToArray());

        }

    }
}
